#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
#include <errno.h>

#include "netlist.h"
#include "hash.h"
#include "logicports.h"
#include "simulation.h"
#include "print.h"

int main(int argc, char ** argv)
{
	time_t start, end;
	time(&start);
	double main_time = 0.0;
	double sim_time = 0.0;
	double print_time = 0.0;

	// File to be opened. Right now it's hard coded for debugging.
	int c;
	combinational_type sim_type = None;
	int hasIn = 0;
	int hasBench = 0;
	int hasOut = 0;

	char *in_filename;
	char *out_filename;
	char *bench_filename;

	while((c = getopt(argc, argv, "n:t:i:o:")) != -1){
		switch(c){
		case 'n':
			hasIn = 1;
			in_filename = optarg;
			break;
		case 't':
			if(!strcmp(optarg, "user_defined")){
				printf("User defined simulation type.\n");
				sim_type = User;
			}else if(!strcmp(optarg, "exhaustive")){
				printf("Exhaustive simulation type.\n");
				sim_type = Exhaustive;
			}else if(!strcmp(optarg, "testbench")){
				printf("TestBench simulation type.\n");
				sim_type = Testbench;
			}else{
				printf("Invalid simulation type.\n");
			}
			break;
		case 'i':
			hasBench = 1;
			bench_filename = optarg;
			break;
		case 'o':
			hasOut = 1;
			out_filename = optarg;
			break;
		}

	}

	// If no simulation
	if(sim_type==None){
		printf("No or invalid simulation type given.\n");
		print_usage(argv[0]);
		return 0;
	}

	//
	if(!hasIn){
		printf("No input file given.\n");
		print_usage(argv[0]);
		return 0;
	}

	//
	if((sim_type==Testbench) && !hasBench){
		printf("No test bench file given.\n");
		print_usage(argv[0]);
		return 0;
	}

	//
	if((sim_type==Testbench) && !hasOut){
		out_filename = malloc(sizeof(char)*strlen("sim_result.txt"));
		strcpy(out_filename, "sim_result.txt");
	}

	FILE *in_file = fopen(in_filename, "r");
	if(!in_file){
		printf("Error opening file: %s\n", in_filename);
		printf("%s", strerror(errno));
		return 0;
	}
	// The circuit interface specifications (outputs and inputs).
	s_interface *myinterface = malloc(sizeof(s_interface));
	// The hash table containing all the elements.
	node **table = create_hash_table(100);
	// Read the netlist.
	read_netlist(in_file, myinterface, table, 100);


	FILE *bench_file;
	FILE *out_file;

	switch(sim_type){
	case None:
		// Did you really came here? Should not be possible.
		break;
	case User:
		sim_time = user_sim(myinterface);
		printf("User simulation finished.\n");
		break;
	case Exhaustive:
		sim_time = exhaustive_sim(myinterface);
		printf("Exhaustive simulation finished.\n");
		break;
	case Testbench:
		bench_file = fopen(bench_filename, "r");
		if(!bench_file){
			printf("Error opening file: %s\n", bench_filename);
			printf("%s", strerror(errno));
			return 0;
		}
		out_file = fopen(out_filename, "w");
		sim_time = testbench_sim(bench_file, myinterface, out_file);
		break;
	}

	print_time = print_timing_information(table, 100);

	// Clean every thing up.
	clean_hash(table, 100);
	free(myinterface->inputs);
	free(myinterface->outputs);
	free(myinterface);
	free(table);
	// Calculate time diff.
	time(&end);
	main_time = difftime(end,start)*1000;
	// Print timing info.
	printf("Time required to perform the simulation: %.2lf ms.\n", sim_time);
	printf("Time required to print the delay information: %.2lf ms.\n", print_time);
	printf("Total application time: %.2lf ms.\n", main_time);

	return 0;
}

int vector_or_gate(int inputs[], int size)
{
	int temp = LOGIC_X;
	int i=0;
	for(i=1;i<size;i++)
	{
		if(i == 1)
		{
			temp = or_gate(inputs[0],inputs[1]);
		}
		else
		{
			temp = or_gate(temp,inputs[i]);
		}
	}
	return temp;
}

int vector_nor_gate(int inputs[], int size)
{
	int temp = LOGIC_X;
	int i=0;
	for(i=1;i<size;i++)
	{
		if(i == 1)
		{
			temp = nor_gate(inputs[0],inputs[1]);
		}
		else
		{
			temp = nor_gate(temp,inputs[i]);
		}
	}
	return temp;
}

int vector_and_gate(int inputs[], int size)
{
	int temp = LOGIC_X;
	int i;
	for(i=1;i<size;i++)
	{
		if(i == 1)
		{
			temp = and_gate(inputs[0],inputs[1]);
		}
		else
		{
			temp = and_gate(temp,inputs[i]);
		}
	}
	return temp;
}

int vector_nand_gate(int inputs[], int size)
{
	int temp = LOGIC_X;
	int i;
	for(i=1;i<size;i++)
	{
		if(i == 1)
		{
			temp = nand_gate(inputs[0],inputs[1]);
		}
		else
		{
			temp = nand_gate(temp,inputs[i]);
		}
	}
	return temp;
}

int vector_xor_gate(int inputs[], int size)
{
	int temp = LOGIC_X;
	int i;
	for(i=1;i<size;i++)
	{
		if(i == 1)
		{
			temp = xor_gate(inputs[0],inputs[1]);
		}
		else
		{
			temp = xor_gate(temp,inputs[i]);
		}
	}
	return temp;
}

int vector_xnor_gate(int inputs[], int size)
{
	int temp = LOGIC_X;
	int i;
	for(i=1;i<size;i++)
	{
		if(i == 1)
		{
			temp = xnor_gate(inputs[0],inputs[1]);
		}
		else
		{
			temp = xnor_gate(temp,inputs[i]);
		}
	}
	return temp;
}

double print_timing_information(node *table[], int table_size)
{
	time_t start, end;
	int i =0;
	double delay = 0;
	double dif = 0;
	char name[LINE_BUF_SIZE];

	time(&start);
	printf("<type>\t<name>\t<delay>\n");

	/*
	 * First print all wires and their delays
	 */
	for(i=0;i<table_size;i++)
	{
		node *tmp =table[i];

		while(tmp != NULL)
		{
			if(tmp->net_item->type == Wire)
			{
				delay = GET_WIRE(tmp->net_item).delay;
				strcpy(name, tmp->net_item->name);
				//name = tmp->net_item->name;
				printf("Wire\t%s\t%f\n",name,delay);
			}
			tmp = tmp->next;
		}
	}

	/*
	 * Then print all gates and their delays
	 */
	for(i=0;i<table_size;i++)
	{
		node *tmp = table[i];

		while(tmp != NULL)
		{
			if(tmp->net_item->type == Gate)
			{
				delay = GET_GATE(tmp->net_item).delay;
				strcpy(name, tmp->net_item->name);
				//				name = tmp->net_item->name;
				printf("Gate\t%s\t%f\n",name,delay);
			}
			tmp = tmp->next;
		}
	}
	time(&end);
	dif = difftime(end,start)*1000;
	return dif;
}

void schedule_event(event **event_list, double time_stamp, circuit_item *item, int update_value)
{

	event *new_event;

	new_event = malloc(sizeof(event));
	new_event->item = item;
	new_event->time_stamp = time_stamp;
	new_event->update_value = update_value;
	// Initializing the next_event with 0 is just a good-practice, according to memory check tool Valgrind.
	new_event->next_event = 0;
	if(!event_list[0]){
		event_list[0]=new_event;
		return;
	}

	event *tmp = event_list[0];

	int ended = 0;

	while(!ended)
	{
		/*
		 * Check if the new event should be in the beginning of the event list.
		 */
		if(tmp->time_stamp > time_stamp)
		{
			event_list[0] = new_event; //Check oftie op plaats 1 moet komen zo, ja moet event list gaan pointen naar het adres van new_event maar doe ik dat zo?
			new_event->next_event=tmp;
			ended = 1;
		}
		/*
		 * Check if the last element is reached, when this is the case the new event should be added at the end of the event list.
		 */
		else if(!tmp->next_event)
		{
			tmp->next_event = new_event;
			new_event->next_event = NULL;
			ended = 1;
		}
		/*
		 * If the time stamp of the next event is lower or equal to the time stamp of the new event this is not the place where it should be inserted so go to the next item.
		 */
		else if(tmp->next_event->time_stamp<=time_stamp)
		{
			tmp = tmp->next_event;
		}
		/*
		 * Else the new event should be inserted after the current event.
		 */
		else
		{
			new_event->next_event=tmp->next_event;
			tmp->next_event = new_event;
			ended = 1;
		}
	}
}

void add_input_events(int inputs[], int bitmask[], double time_stamp, s_interface *interface,event **event_list)
{
	int no_inputs = interface->no_inputs;
	int i =0;
	double delay;

	for(i = 0; i<no_inputs; i++)
	{
		if(bitmask[i]==1)
		{
			/*
			 * the right time stamp = time_stamp + delay of the wire
			 */
			delay=interface->inputs[i]->cir_type.circuit_wire.delay;
			schedule_event(event_list,time_stamp+delay,interface->inputs[i],inputs[i]);
		}
	}
}


void sim_single_event(event **event_list)
{
	event *tmp = event_list[0];

	int no_outputs = GET_WIRE(tmp->item).no_outputs;


	//Update the wire value
	GET_WIRE(tmp->item).value = tmp->update_value;

	// Loop through all the gates connected to this wire (as an output of this wire)
	int i=0;
	for(i=0; i<no_outputs; i++)
	{
		double timestamp = 0.0;
		circuit_item *tmp_gate = GET_WIRE(tmp->item).out[i];
		int no_inputs = GET_GATE(tmp_gate).no_inputs;
		int values[no_inputs];
		int j = 0;
		// Get the values from all the circuits.
		for(j = 0; j < no_inputs; j++){
			circuit_item *tmp_in_wire = GET_GATE(tmp_gate).in[j];
			values[j] = GET_WIRE(tmp_in_wire).value;
		}

		int output = LOGIC_X;
		switch(GET_GATE(tmp_gate).type){
		case Or: output = vector_or_gate(values,no_inputs);
		break;
		case Nor: output = vector_nor_gate(values,no_inputs);
		break;
		case And: output = vector_and_gate(values,no_inputs);
		break;
		case Nand: output = vector_nand_gate(values,no_inputs);
		break;
		case Xor: output = vector_xor_gate(values,no_inputs);
		break;
		case Xnor: output = vector_xnor_gate(values,no_inputs);
		break;
		case Not: output = not_gate(values[0]);
		break;
		}
		timestamp = tmp->time_stamp + GET_GATE(tmp_gate).delay + GET_WIRE(GET_GATE(tmp_gate).out).delay;
		schedule_event(event_list,timestamp,GET_GATE(tmp_gate).out,output);
	}
	event_list[0] = tmp->next_event;
	free(tmp);
}

void sim_all_events(event **event_list)
{
	while(event_list[0])
	{
		sim_single_event(event_list);
	}
}

double user_sim(s_interface *interface)
{
	time_t start, end;
	double dif = 0.0;

	int noinputs = interface->no_inputs;
	int nooutputs = interface->no_outputs;

	char input[noinputs];
	int logicvalues[noinputs];
	int bitmask[noinputs];
	int output[nooutputs];

	int length = 0;
	int ended = 0;

	time(&start);

	event **event_list = malloc(sizeof(event*));
	event_list[0] = 0;


	printf("Please give %d input values: \n",noinputs);
	scanf("%s",input);
	int i;

	while(!ended)
	{

		length = strlen(input);

		if(length != noinputs)
		{
			int difference = noinputs-length;

			if(difference<0)
			{
				printf("The input is %d value(s) too long (needed length is %d), please retry: \n",-difference,noinputs);
			}
			else if(difference>0)
			{
				printf("The input is %d value(s) too short (needed length is %d), please retry: \n", difference,noinputs);
			}
			scanf("%s",input);
			ended = 0;
		}
		else
		{
			ended = 1;
		}
	}


	/*
	 * Convert character input to values that can be handled by the circuit; create bitmask with 1s at the same time.
	 */
	for(i=0;i<noinputs;i++)
	{
		logicvalues[i]=char_to_logic(input[i]);
		ended = 0;
		char replace = 'U';
		while(!ended)
		{
			if(logicvalues[i]==-1)
			{
				printf("An invalid value (%c) was entered on place %d. Please enter a new value for place %d (0,1 or X): \n",input[i],i,i);
				scanf("%c",&replace);
				logicvalues[i] = char_to_logic(replace);
				ended = 0;
			}
			else
			{
				ended =1;
			}
		}
		bitmask[i]=1;
	}

	/*
	 * Add all events to the event list
	 */
	add_input_events(logicvalues,bitmask,0.0,interface,event_list);

	/*
	 * Simulate all the events that are in the event list
	 */
	sim_all_events(event_list);

	/*
	 * Get all the output values of the wires
	 */
	for(i=0;i<nooutputs;i++)
	{
		output[i]=interface->outputs[i]->cir_type.circuit_wire.value;
	}

	/*
	 * Print the result of the user simulation.
	 */
	printf("\n %s","The output of the logic circuit is: ");
	print_vector(output,nooutputs);
	printf("\n");
	free(event_list);

	time(&end);
	dif = difftime(end,start)*1000;
	return dif;
}

double exhaustive_sim(s_interface *interface)
{
	time_t start, end;
	double dif = 0.0;

	//Make new event list
	event **event_list = malloc(sizeof(event*));
	event_list[0] = 0;

	int noinputs = interface->no_inputs;
	int nooutputs = interface->no_outputs;

	int logicvalues[noinputs];
	int bitmask[noinputs];
	int output[nooutputs];

	int i,j;
	time(&start);

	/*
	 * Create all possible inputs and simulate them.
	 */
	for(i=0;i<(1 << noinputs);i++)
	{
		for(j=0;j<noinputs;j++)
		{
			logicvalues[j]=(i & (1 << j))>>j;
			bitmask[j]=1;
		}

		/*
		 * Add all events to the event list
		 */
		add_input_events(logicvalues,bitmask,0.0,interface,event_list);

		/*
		 * Simulate all the events that are in the event list
		 */
		sim_all_events(event_list);

		/*
		 * Get all the output values of the wires
		 */
		for(j=0;j<nooutputs;j++)
		{
			output[j]=GET_WIRE(interface->outputs[j]).value;
		}

		/*
		 * Print the result of the current simulation.
		 */
		print_vector(logicvalues,noinputs);
		printf(" --> ");
		print_vector(output,nooutputs);
		printf("\n");
	}

	free(event_list);
	time(&end);
	dif = difftime(end,start)*1000;
	return dif;
}

void init_TB_based_events(FILE *fp_tb,s_interface *interface,event **event_list)
{
	char line[LINE_BUF_SIZE];
	double delay;
	int noinputs = interface->no_inputs;
	int logicvalues[noinputs];
	int bitmask[noinputs];
	int i;
	// Initialize all the inputs.
	for(i=0; i < noinputs; i++){
		logicvalues[i]=LOGIC_X;
	}
	rewind(fp_tb);

	read_line(fp_tb,line);
	while(strlen(line))
	{
		if(line[0]=='%')
		{
			read_line(fp_tb,line);
		}
		else
		{
			char *inputvector = strtok(line, "\t ");
			char *inputdelay = strtok(NULL, "\t ");
			for(i=0;i<noinputs;i++)
			{
				if(char_to_logic(inputvector[noinputs-1-i])==logicvalues[noinputs-1-i])
				{
					bitmask[i] = 0;
				}
				else
				{
					bitmask[i] = 1;
				}
				logicvalues[noinputs-1-i] = char_to_logic(inputvector[noinputs-1-i]);
			}

			delay = strtod(inputdelay,NULL);

			add_input_events(logicvalues,bitmask,delay,interface,event_list);

			read_line(fp_tb,line);
		}
	}
}

double testbench_sim(FILE *fp_tb, s_interface *interface, FILE *fp_out)
{
	time_t start, end;
	double dif = 0.0;

	int nooutputs = interface->no_outputs;
	int oldoutputs[nooutputs];
	int newoutputs[nooutputs];
	char txt_vector[nooutputs];

	int i;

	time(&start);

	//Make new event list
	event **event_list = malloc(sizeof(event*));
	event_list[0] = 0;
	init_TB_based_events(fp_tb,interface,event_list);

	//
	for(i=0;i<nooutputs;i++)
	{
		newoutputs[i]=interface->outputs[i]->cir_type.circuit_wire.value;
	}
	for(i=0;i<nooutputs;i++)
	{
		txt_vector[i] = logic_to_char(newoutputs[nooutputs-1-i]);
	}
	txt_vector[i] = '\0';
	fprintf(fp_out,"\%Output\tTime\n");
	fprintf(fp_out,"%s\t%f\n",txt_vector,0.0);


	// While there are events to simulate.
	while(event_list[0])
	{
		// Save the time-stamp, as we are going to delete this event
		// after simulating.
		double timestamp = event_list[0]->time_stamp;
		for(i=0;i<nooutputs;i++)
		{
			oldoutputs[i]=interface->outputs[i]->cir_type.circuit_wire.value;
		}
		sim_single_event(event_list);
		for(i=0;i<nooutputs;i++)
		{
			newoutputs[i]=interface->outputs[i]->cir_type.circuit_wire.value;
		}
		// If output is different, print it.
		if(!compare_arrays(oldoutputs,newoutputs,nooutputs))
		{
			for(i=0;i<nooutputs;i++)
			{
				txt_vector[i] = logic_to_char(newoutputs[nooutputs-1-i]);
			}
			txt_vector[i] = '\0';
			fprintf(fp_out,"%s\t%f\n",txt_vector,timestamp);
		}

	}

	time(&end);
	free(event_list);
	dif = difftime(end,start)*1000;
	return dif;
}

int compare_arrays(int *a,int *b, int size)
{
	int i;

	for(i=0;i<size;i++)
	{
		if(a[i] != b[i])
		{
			return 0;
			break;
		}
	}
	return 1;
}

void print_usage(char *program_name)
{
	printf("This program can run in three different simulation modes: \n");
	printf("'User defined': %s -n <netlist_name> –t user_defined \n", program_name);
	printf("'Exhaustive': %s -n < netlist_name> –t exhaustive \n", program_name);
	printf("'Testbench': %s -n < netlist_name> –t testbench –i <testbench_file> [–o <output_response_file>]\n", program_name);
	printf("executable name: denotes the executable file\n");
	printf("-n : this option specifies the input file of the simulator consisting of the netlist \n");
	printf("-t : this option specifies the simulation mode \n");
	printf("-i : this option specifies the input file name containing the testbench. This is only applicable to the 'Testbench' simulation.\n");
	printf("-o: this option specifies the output response file. Note that this argument is optional. In case not provided, a default filename “sim_result.txt” is used \n");

}
